package net.xiaohanlin.smartmetric.registry;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricSet;
import com.codahale.metrics.ScheduledReporter;

import metrics_influxdb.InfluxdbReporter;
import metrics_influxdb.InfluxdbReporter.Builder;
import metrics_influxdb.api.protocols.HttpInfluxdbProtocol;
import net.xiaohanlin.smartmetric.config.InlfuxdbReporterConfiguration;
import net.xiaohanlin.smartmetric.config.MetricRegistryConfiguration;
import net.xiaohanlin.smartmetric.config.MetricRegistryConfiguration.MetricClassConfiguration;
import net.xiaohanlin.smartmetric.config.ReporterConfiguration;
import net.xiaohanlin.smartmetric.config.ScheduledExecutorServiceConfiguration;
import net.xiaohanlin.smartmetric.config.SmartMetricConfigService;
import net.xiaohanlin.smartmetric.config.SmartMetricConfiguration;
import net.xiaohanlin.smartmetric.metricname.MetricNameService;
import net.xiaohanlin.smartmetric.reporter.SmartMetricsConsoleReporter;
import net.xiaohanlin.smartmetric.reporter.SmartMetricsInfluxdbReporter;
import net.xiaohanlin.smartmetric.reporter.SmartMetricsScheduleReporter;

/**
 * 主类。
 * 
 * @author jiaozi
 */
public class SmartMetric {

	private volatile boolean initialized = false;
	private volatile boolean disable = false;
	private ConcurrentMap<String, ScheduledExecutorService> ScheduledExecutorServices = null;
	private ConcurrentMap<String, SmartMetricRegistry> MetricRegistrys = null;
	private ConcurrentHashMap<String, SmartMetricsScheduleReporter> ScheduledReporters = null;
	private SmartMetricConfiguration smartMetricConfiguration;

	private static SmartMetric instance = new SmartMetric();

	private SmartMetric() {
	}

	public static SmartMetric instance() {
		return instance;
	}

	public Set<String> getMetricRegistryNames() {
		return MetricRegistrys.keySet();
	}

	public SmartMetricRegistry getMetricRegistry(String name) {
		return MetricRegistrys.get(name);
	}

	public void readConfiguration(String fileName) {
		this.smartMetricConfiguration = SmartMetricConfigService.instance.readConfigurations(fileName);
	}

	public synchronized void start() {
		if (!initialized) {
			Objects.requireNonNull(this.smartMetricConfiguration);
			initScheduledExecutorService();
			initMetricsRegistry();
			initMetricsReporter();
			startReporter();
			initialized = true;
		}
	}

	private void startReporter() {
		for (SmartMetricsScheduleReporter smartMetricsScheduleReporter : ScheduledReporters.values()) {
			smartMetricsScheduleReporter.scheduleSmartMetricsReporter();
		}
	}

	public synchronized void stop() {
		if (initialized && !disable) {
			for (ScheduledExecutorService executor : ScheduledExecutorServices.values()) {
				executor.shutdown();
				try {
					if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
						executor.shutdownNow();
						if (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
							System.err.println(getClass().getSimpleName() + ": ScheduledExecutorService did not terminate");
						}
					}
				} catch (InterruptedException ie) {
					executor.shutdownNow();
					Thread.currentThread().interrupt();
				}
			}
			disable = true;
		}
	}

	private void initMetricsReporter() {
		List<ReporterConfiguration> reporterConfigurationList = this.smartMetricConfiguration.getReporterConfiguration();
		this.ScheduledReporters = new ConcurrentHashMap<>(reporterConfigurationList.size());
		for (ReporterConfiguration reporterConfiguration : reporterConfigurationList) {
			String forMetricRegistry = reporterConfiguration.getForMetricRegistry();
			SmartMetricRegistry metricRegistry = MetricRegistrys.get(forMetricRegistry);
			if (metricRegistry == null) {
				throw new ExceptionInInitializerError("cannot find MetricRegistry " + forMetricRegistry + " for reporter : " + reporterConfiguration.getId());
			}
			ScheduledExecutorService scheduledExecutorService = this.ScheduledExecutorServices.get(reporterConfiguration.getScheduledExecutorService());
			if (scheduledExecutorService == null) {
				throw new ExceptionInInitializerError("cannot find ScheduledExecutorService for reporter : " + reporterConfiguration.getId());
			}

			SmartMetricsScheduleReporter reporter = null;
			switch (reporterConfiguration.getSmartReporterType()) {
				case console:
					reporter = SmartMetricsConsoleReporter.forRegistry(metricRegistry.getMetricRegistry(), scheduledExecutorService).build().setPeriod(reporterConfiguration.getPeriod()).setTimeUnit(TimeUnit.valueOf(reporterConfiguration.getTimeUnit()));
					break;
				case influxdb:
					InlfuxdbReporterConfiguration irc = (InlfuxdbReporterConfiguration) reporterConfiguration;
					HttpInfluxdbProtocol httpInfluxdbProtocol = new HttpInfluxdbProtocol(irc.getHost(), irc.getPort(), irc.getUsername(), irc.getPassword(), irc.getDbname());
					Builder builder = InfluxdbReporter.forRegistry(metricRegistry.getMetricRegistry());
					ScheduledReporter scheduledReporter = builder.withScheduler(scheduledExecutorService).protocol(httpInfluxdbProtocol).build();
					reporter = new SmartMetricsInfluxdbReporter(reporterConfiguration.getPeriod(), TimeUnit.valueOf(reporterConfiguration.getTimeUnit()), scheduledReporter);
					break;
				default:
					break;
			}

			if (reporter == null) {
				throw new ExceptionInInitializerError("cannot find constructe reporter : " + reporterConfiguration.getId());
			}
			this.ScheduledReporters.put(reporterConfiguration.getId(), reporter);
		}
	}

	private void initScheduledExecutorService() {
		// init ScheduledExecutorService
		List<ScheduledExecutorServiceConfiguration> scheduledExecutorServiceList = this.smartMetricConfiguration.getScheduledExecutorServiceList();
		this.ScheduledExecutorServices = new ConcurrentHashMap<>(scheduledExecutorServiceList.size());
		for (ScheduledExecutorServiceConfiguration scheduledExecutorServiceConfiguration : scheduledExecutorServiceList) {
			String id = scheduledExecutorServiceConfiguration.getId();
			int threadCount = scheduledExecutorServiceConfiguration.getThreadCount();
			ScheduledExecutorService es = null;
			if (threadCount == 1) {
				es = Executors.newSingleThreadScheduledExecutor(new SmartMetricNamedThreadFactory(scheduledExecutorServiceConfiguration.getThreadNamePrefix()));
			} else {
				es = Executors.newScheduledThreadPool(scheduledExecutorServiceConfiguration.getThreadCount(), new SmartMetricNamedThreadFactory(scheduledExecutorServiceConfiguration.getThreadNamePrefix()));
			}
			ScheduledExecutorServices.put(id, es);
		}
	}

	private void initMetricsRegistry() {
		// init metric registrys
		List<MetricRegistryConfiguration> metricRegistryList = this.smartMetricConfiguration.getMetricRegistryList();
		this.MetricRegistrys = new ConcurrentHashMap<>(metricRegistryList.size());
		for (MetricRegistryConfiguration metricsRegistryConfiguration : metricRegistryList) {
			// create SmartMetricsRegistry
			SmartMetricRegistry smartMetricsRegistry = new SmartMetricRegistry(metricsRegistryConfiguration.getName(), metricsRegistryConfiguration.getMetricPrefix(), metricsRegistryConfiguration.getMetricSuffix());
			MetricRegistrys.put(metricsRegistryConfiguration.getName(), smartMetricsRegistry);
			// create SmartMetricsRegistry metrics
			Map<String, MetricClassConfiguration> metricClassConfigs = metricsRegistryConfiguration.getMetricClassConfigs();
			for (MetricClassConfiguration mcc : metricClassConfigs.values()) {
				String cls = mcc.getCls();
				try {
					MetricSet ms = (MetricSet) Class.forName(cls).newInstance();
					Map<String, Metric> metrics = ms.getMetrics();

					// iterate each metric
					for (Map.Entry<String, Metric> entry : metrics.entrySet()) {
						String name = entry.getKey();
						String metricName = contructureMetricName(name, mcc, metricsRegistryConfiguration);
						smartMetricsRegistry.getMetricRegistry().register(metricName, entry.getValue());
					}

				} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	private String contructureMetricName(String name, MetricClassConfiguration mcc, MetricRegistryConfiguration metricsRegistryConfiguration) {
		StringBuilder sb = new StringBuilder();
		if (metricsRegistryConfiguration.getMetricPrefix() != null) {
			String replaceVariables = MetricNameService.INSTANCE.replaceVariables(metricsRegistryConfiguration.getMetricPrefix());
			sb.append(replaceVariables);
		}

		if (mcc.getPrefix() != null) {
			String replaceVariables = MetricNameService.INSTANCE.replaceVariables(mcc.getPrefix());
			sb.append(replaceVariables);
		}

		sb.append(name);

		if (mcc.getSuffix() != null) {
			String replaceVariables = MetricNameService.INSTANCE.replaceVariables(mcc.getSuffix());
			sb.append(replaceVariables);
		}

		if (metricsRegistryConfiguration.getMetricSuffix() != null) {
			String replaceVariables = MetricNameService.INSTANCE.replaceVariables(metricsRegistryConfiguration.getMetricSuffix());
			sb.append(replaceVariables);
		}

		return sb.toString();

	}

}
